Make a run say what it did without - #210
Merged
Merged
Conversation
Several boundaries here deliberately refuse to let a host-supplied callback end a run: a broken logging sink or observer must not destroy a user's work. That resilience is correct. It was paid for with a warnings.warn that reaches no consumer of the result, so a run that lost a budget event, ran its terminal extract without host context, or discarded salvageable work returned something byte-identical to a clean run. The loss was real and unobservable. A demonstration, before this change: give BudgetLedger a sink that raises once mid-stream and four reservations produce three delivered events, a journal of four, an _emitted_events of four, and one RuntimeWarning. The event is gone, the ledger believes it was delivered, and every consumer sees a stream that is simply one event short. Add RunDegradation -- site, error_type, detail, and consequence, the last recording not that a callback raised but what the run then did without. Recorded at budget_event_sink, extract_context_provider, and extractable_work_probe; exposed on RLMResult.degradations and, via Trace ABI v10, on the result event so a host across the wire sees it too. Empty on a clean run and always present: absence must never be how a consumer learns nothing was lost. Nothing new can end a run. The fallbacks are unchanged; only their silence is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The done event reports the run's terminal shape, so a host that reads only that event would have learned nothing about what the run did without. Add the field there too, in both the Python schema and the relay's, and drive the CI corpus checks off the enumeration rather than naming helpers -- naming them is what left release.yml behind on v9 and this workflow behind on v10. Also fixes fixtures I regenerated with Python's default ensure_ascii, which escaped a literal em dash to — and changed bytes the conformance corpus is compared against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Several boundaries deliberately refuse to let a host-supplied callback end a run — a broken logging sink must not destroy a user's work. That resilience is correct. It was paid for with a
warnings.warnthat reaches no consumer of the result.Demonstrated before this change — a sink that raises once, mid-stream:
The event is gone, the ledger believes it was delivered, and every consumer sees a stream that is simply one event short. A run that lost an event, ran its terminal extract without host context, or discarded salvageable work returned something byte-identical to a clean run.
What
RunDegradation—site,error_type,detail, andconsequence, the last recording not that a callback raised but what the run then did without:budget_event_sinkextract_context_providerextractable_work_probeExposed on
RLMResult.degradationsand — via Trace ABI v9 → v10 — on theresultevent, so a host across the wire sees it too.BudgetLedger.dropped_events()exposes the same facts for a directly-held ledger.Empty on a clean run and always present: absence must never be how a consumer learns nothing was lost.
droste_runneremitsdegradations: []on every response shape for the same reason.Nothing new can end a run. The fallbacks are unchanged; only their silence is.
On the ABI bump
The nested
resultpayload is a closed schema despite a comment claiming it "keeps its own schema authority" — adding the field is necessarily an ABI change. TS treats the nested result as opaque, so the relay needed only the envelope bump.Verification
1268 passed, 3 skipped; 65 Deno tests. Includes an end-to-end test asserting a recorded degradation actually arrives on the emitted
resultevent — recording something the host never receives would be the same defect one layer up.🤖 Generated with Claude Code